﻿/*----------------------------------------------------------------
//  <copyright file="MailKitSender.cs" company="MicroCloud@151504200868">
//      Copyright © 2020-2024 MicroCloud Corporation, All rights reserved.
//  </copyright>
//  <site>https://gitee.com/chenmm123/microclouds</site>
//  <last-editor>cmm</last-editor>
//  <last-date>2023-12-06 10:50</last-date>
//----------------------------------------------------------------*/

using MimeKit;
using MimeKit.Text;

namespace MicroCloud
{
    #region "MailKit邮件发送者"
    /// <summary>
    /// MailKit邮件发送者
    /// </summary>
    public class MailKitSender : IEmailSender
    {
        //字段
        private readonly IServiceProvider _provider;
        private readonly ILogger<MailKitSender> _logger;
        private readonly MicroCloudOptions _options;

        #region "构造函数"
        #region "初始化一个MailKit邮件发送者 <see cref="MailKitSender"/> 的新实例"
        /// <summary>
        /// 初始化一个MailKit邮件发送者 <see cref="MailKitSender"/> 的新实例
        /// </summary>
        /// <param name="provider">服务提供者</param>
        public MailKitSender(IServiceProvider provider)
        {
            _provider = provider;
            _logger = _provider.GetLogger<MailKitSender>();
            _options = _provider.GetMicroCloudOptions();
        }
        #endregion

        #endregion

        #region "方法"
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="attachments">邮件附件</param>
        /// <param name="toEmails">收件人</param>
        /// <param name="ccEmails">抄送</param>
        /// <param name="bccEmails">密送</param>
        /// <param name="options">邮件配置信息</param>
        public virtual void SendEmail(string subject, string body, EmailBodyFormat bodyFormat, EmailAttachmentDto[] attachments, string[] toEmails, string[] ccEmails, string[] bccEmails, EmailSenderOptions options = null)
        {
            var mailSender = options ?? (_options?.Email?.Sender);
            if (mailSender == null || mailSender.Host == null || mailSender.Host.Contains("请替换"))
            {
                throw new Exception(I18N.T("邮件发送选项不存在，请在 appsettings.json 配置 MicroCloud:Email:Sender 节点"));
            }

            string host = mailSender.Host,
                displayName = mailSender.DisplayName,
                userName = mailSender.UserName,
                password = mailSender.Password;
            bool enableSsl = mailSender.EnableSsl;
            int port = mailSender.Port;
            if (port <= 0)
            {
                port = enableSsl ? 465 : 25;
            }
            var textFormat = (TextFormat)bodyFormat;

            string fromEmail = userName.Contains('@') ? userName : "{0}@{1}".FormatWith(userName, host.Replace("smtp.", ""));
            MailboxAddress sender = new(displayName, fromEmail);
            MimeMessage mime = new()
            {
                Sender = sender,
                Subject = subject,
                //Body = new TextPart(textFormat) { Text = body }
            };
            //发送自
            mime.From.Add(sender);
            //收件人
            if (toEmails == null || toEmails.Length <= 0)
            {
                throw new Exception(I18N.T("邮件收件人不能为空"));
            }
            foreach (var toEmail in toEmails)
            {
                mime.To.Add(new MailboxAddress("", toEmail));
            }
            //抄送
            if (ccEmails != null && ccEmails.Length > 0)
            {
                foreach (var ccEmail in ccEmails)
                {
                    mime.Cc.Add(new MailboxAddress("", ccEmail));
                }
            }
            //密送
            if (bccEmails != null && bccEmails.Length > 0)
            {
                foreach (var bccEmail in bccEmails)
                {
                    mime.Bcc.Add(new MailboxAddress("", bccEmail));
                }
            }
            //邮件正文
            var alternative = new Multipart() { new TextPart(textFormat) { Text = body } };
            var multipart = new Multipart { alternative };
            //邮件附件
            if (attachments != null && attachments?.Length > 0)
            {
                foreach (var attachment in attachments)
                {
                    multipart.Add(AddAttachment(attachment.FileName, attachment.FileBase64.ToStreamFromBase64String(), attachment.MediaType, attachment.MediaSubType));
                }
            }
            //处理发送
            mime.Body = multipart;
            using var smtp = new MailKit.Net.Smtp.SmtpClient();
            smtp.MessageSent += (s, e) =>
            {
                var ccStr = "";
                if (mime.Cc.Count > 0)
                {
                    ccStr = $"，抄送到“{mime.Cc.ExpandAndToString()}”";
                }
                var bccStr = "";
                if (mime.Bcc.Count > 0)
                {
                    bccStr = $"，密送到“{mime.Bcc.ExpandAndToString()}”";
                }
                _logger.LogDebug("发送邮件到 {to} {ccStr}{bccStr}，标题：{subject}，结果：{response}。", mime.To.ExpandAndToString(), ccStr, bccStr, mime.Subject, e.Response);
            };
            smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
            smtp.Connect(host, port, enableSsl);
            smtp.Authenticate(userName, password);
            smtp.Send(mime);
            smtp.Disconnect(true);
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="attachments">邮件附件</param>
        /// <param name="toEmails">收件人</param>
        /// <param name="ccEmails">抄送</param>
        /// <param name="options">邮件配置信息</param>
        public virtual void SendEmail(string subject, string body, EmailBodyFormat bodyFormat, EmailAttachmentDto[] attachments, string[] toEmails, string[] ccEmails, EmailSenderOptions options = null)
        {
            SendEmail(subject, body, bodyFormat, attachments, toEmails, ccEmails, null, options);
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="attachments">邮件附件</param>
        /// <param name="options">邮件配置信息</param>
        /// <param name="toEmails">收件人</param>
        public virtual void SendEmail(string subject, string body, EmailBodyFormat bodyFormat, EmailAttachmentDto[] attachments, EmailSenderOptions options, params string[] toEmails)
        {
            SendEmail(subject, body, bodyFormat, attachments, toEmails, null, null, options);
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="attachments">邮件附件</param>
        /// <param name="toEmails">收件人</param>
        public virtual void SendEmail(string subject, string body, EmailBodyFormat bodyFormat, EmailAttachmentDto[] attachments, params string[] toEmails)
        {
            SendEmail(subject, body, bodyFormat, attachments, toEmails, null, null, null);
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="toEmails">收件人</param>
        /// <param name="ccEmails">抄送</param>
        /// <param name="bccEmails">密送</param>
        /// <param name="options">邮件配置信息</param>
        public virtual void SendEmail(string subject, string body, EmailBodyFormat bodyFormat, string[] toEmails, string[] ccEmails, string[] bccEmails, EmailSenderOptions options = null)
        {
            SendEmail(subject, body, bodyFormat, null, toEmails, ccEmails, bccEmails, options);
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="toEmails">收件人</param>
        /// <param name="ccEmails">抄送</param>
        /// <param name="options">邮件配置信息</param>
        public virtual void SendEmail(string subject, string body, EmailBodyFormat bodyFormat, string[] toEmails, string[] ccEmails, EmailSenderOptions options = null)
        {
            SendEmail(subject, body, bodyFormat, null, toEmails, ccEmails, null, options);
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="options">邮件配置信息</param>
        /// <param name="toEmails">收件人</param>
        public virtual void SendEmail(string subject, string body, EmailBodyFormat bodyFormat, EmailSenderOptions options, params string[] toEmails)
        {
            SendEmail(subject, body, bodyFormat, null, toEmails, null, null, options);
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="toEmails">收件人</param>
        public virtual void SendEmail(string subject, string body, EmailBodyFormat bodyFormat, params string[] toEmails)
        {
            SendEmail(subject, body, bodyFormat, null, toEmails, null, null, null);
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="options">邮件配置信息</param>
        /// <param name="dtos">邮件发送DTO</param>
        public virtual void SendEmail(EmailSenderOptions options, params EmailSendDto[] dtos)
        {
            Check.NotNull(dtos, nameof(dtos));
            foreach (var dto in dtos)
            {
                SendEmail(dto.Subject, dto.Body, dto.BodyFormat, dto.Attachments, dto.ToEmails, dto.CcEmails, dto.BccEmails, options);
            }
        }
        #endregion
        #region "发送邮件"
        /// <summary>
        /// 发送邮件
        /// </summary>
        /// <param name="dtos">邮件发送DTO</param>
        public virtual void SendEmail(params EmailSendDto[] dtos)
        {
            Check.NotNull(dtos, nameof(dtos));
            foreach (var dto in dtos)
            {
                SendEmail(dto.Subject, dto.Body, dto.BodyFormat, dto.Attachments, dto.ToEmails, dto.CcEmails, dto.BccEmails, null);
            }
        }
        #endregion

        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="attachments">邮件附件</param>
        /// <param name="toEmails">收件人</param>
        /// <param name="ccEmails">抄送</param>
        /// <param name="bccEmails">密送</param>
        /// <param name="options">邮件配置信息</param>
        public virtual async Task SendEmailAsync(string subject, string body, EmailBodyFormat bodyFormat, EmailAttachmentDto[] attachments, string[] toEmails, string[] ccEmails, string[] bccEmails, EmailSenderOptions options = null)
        {
            var mailSender = options ?? (_options?.Email?.Sender);
            if (mailSender == null || mailSender.Host == null || mailSender.Host.Contains("请替换"))
            {
                throw new Exception(I18N.T("邮件发送选项不存在，请在 appsettings.json 配置 MicroCloud:Email:Sender 节点"));
            }

            string host = mailSender.Host,
                displayName = mailSender.DisplayName,
                userName = mailSender.UserName,
                password = mailSender.Password;
            bool enableSsl = mailSender.EnableSsl;
            int port = mailSender.Port;
            if (port <= 0)
            {
                port = enableSsl ? 465 : 25;
            }
            var textFormat = (TextFormat)bodyFormat;

            string fromEmail = userName.Contains('@') ? userName : "{0}@{1}".FormatWith(userName, host.Replace("smtp.", ""));
            MailboxAddress sender = new(displayName, fromEmail);
            MimeMessage mime = new()
            {
                Sender = sender,
                Subject = subject,
                //Body = new TextPart(textFormat) { Text = body }
            };
            //发送自
            mime.From.Add(sender);
            //收件人
            if (toEmails == null || toEmails.Length <= 0)
            {
                throw new Exception(I18N.T("邮件收件人不能为空"));
            }
            foreach (var toEmail in toEmails)
            {
                mime.To.Add(new MailboxAddress("", toEmail));
            }
            //抄送
            if (ccEmails != null && ccEmails.Length > 0)
            {
                foreach (var ccEmail in ccEmails)
                {
                    mime.Cc.Add(new MailboxAddress("", ccEmail));
                }
            }
            //密送
            if (bccEmails != null && bccEmails.Length > 0)
            {
                foreach (var bccEmail in bccEmails)
                {
                    mime.Bcc.Add(new MailboxAddress("", bccEmail));
                }
            }
            //邮件正文
            var alternative = new Multipart() { new TextPart(textFormat) { Text = body } };
            var multipart = new Multipart { alternative };
            //邮件附件
            if (attachments != null && attachments?.Length > 0)
            {
                foreach (var attachment in attachments)
                {
                    multipart.Add(AddAttachment(attachment.FileName, attachment.FileBase64.ToStreamFromBase64String(), attachment.MediaType, attachment.MediaSubType));
                }
            }
            //处理发送
            mime.Body = multipart;
            using var smtp = new MailKit.Net.Smtp.SmtpClient();
            smtp.MessageSent += (s, e) =>
            {
                var ccStr = "";
                if (mime.Cc.Count > 0)
                {
                    ccStr = $"，抄送到“{mime.Cc.ExpandAndToString()}”";
                }
                var bccStr = "";
                if (mime.Bcc.Count > 0)
                {
                    bccStr = $"，密送到“{mime.Bcc.ExpandAndToString()}”";
                }
                _logger.LogDebug("发送邮件到 {to} {ccStr}{bccStr}，标题：{subject}，结果：{response}。", mime.To.ExpandAndToString(), ccStr, bccStr, mime.Subject, e.Response);
            };
            smtp.ServerCertificateValidationCallback = (s, c, h, e) => true;
            await smtp.ConnectAsync(host, port, enableSsl);
            await smtp.AuthenticateAsync(userName, password);
            await smtp.SendAsync(mime);
            await smtp.DisconnectAsync(true);
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="attachments">邮件附件</param>
        /// <param name="toEmails">收件人</param>
        /// <param name="ccEmails">抄送</param>
        /// <param name="options">邮件配置信息</param>
        public virtual async Task SendEmailAsync(string subject, string body, EmailBodyFormat bodyFormat, EmailAttachmentDto[] attachments, string[] toEmails, string[] ccEmails, EmailSenderOptions options = null)
        {
            await SendEmailAsync(subject, body, bodyFormat, attachments, toEmails, ccEmails, null, options);
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="attachments">邮件附件</param>
        /// <param name="options">邮件配置信息</param>
        /// <param name="toEmails">收件人</param>
        public virtual async Task SendEmailAsync(string subject, string body, EmailBodyFormat bodyFormat, EmailAttachmentDto[] attachments, EmailSenderOptions options, params string[] toEmails)
        {
            await SendEmailAsync(subject, body, bodyFormat, attachments, toEmails, null, null, options);
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="attachments">邮件附件</param>
        /// <param name="toEmails">收件人</param>
        public virtual async Task SendEmailAsync(string subject, string body, EmailBodyFormat bodyFormat, EmailAttachmentDto[] attachments, params string[] toEmails)
        {
            await SendEmailAsync(subject, body, bodyFormat, attachments, toEmails, null, null, null);
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="toEmails">收件人</param>
        /// <param name="ccEmails">抄送</param>
        /// <param name="bccEmails">密送</param>
        /// <param name="options">邮件配置信息</param>
        public virtual async Task SendEmailAsync(string subject, string body, EmailBodyFormat bodyFormat, string[] toEmails, string[] ccEmails, string[] bccEmails, EmailSenderOptions options = null)
        {
            await SendEmailAsync(subject, body, bodyFormat, null, toEmails, ccEmails, bccEmails, options);
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="toEmails">收件人</param>
        /// <param name="ccEmails">抄送</param>
        /// <param name="options">邮件配置信息</param>
        public virtual async Task SendEmailAsync(string subject, string body, EmailBodyFormat bodyFormat, string[] toEmails, string[] ccEmails, EmailSenderOptions options = null)
        {
            await SendEmailAsync(subject, body, bodyFormat, null, toEmails, ccEmails, null, options);
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="options">邮件配置信息</param>
        /// <param name="toEmails">收件人</param>
        public virtual async Task SendEmailAsync(string subject, string body, EmailBodyFormat bodyFormat, EmailSenderOptions options, params string[] toEmails)
        {
            await SendEmailAsync(subject, body, bodyFormat, null, toEmails, null, null, options);
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="subject">邮件主题</param>
        /// <param name="body">邮件正文内容</param>
        /// <param name="bodyFormat">邮件正文格式</param>
        /// <param name="toEmails">收件人</param>
        public virtual async Task SendEmailAsync(string subject, string body, EmailBodyFormat bodyFormat, params string[] toEmails)
        {
            await SendEmailAsync(subject, body, bodyFormat, null, toEmails, null, null, null);
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="options">邮件配置信息</param>
        /// <param name="dtos">邮件发送DTO</param>
        public virtual async Task SendEmailAsync(EmailSenderOptions options, params EmailSendDto[] dtos)
        {
            Check.NotNull(dtos, nameof(dtos));
            foreach (var dto in dtos)
            {
                await SendEmailAsync(dto.Subject, dto.Body, dto.BodyFormat, dto.Attachments, dto.ToEmails, dto.CcEmails, dto.BccEmails, options);
            }
        }
        #endregion
        #region "异步发送邮件"
        /// <summary>
        /// 异步发送邮件
        /// </summary>
        /// <param name="dtos">邮件发送DTO</param>
        public virtual async Task SendEmailAsync(params EmailSendDto[] dtos)
        {
            Check.NotNull(dtos, nameof(dtos));
            foreach (var dto in dtos)
            {
                await SendEmailAsync(dto.Subject, dto.Body, dto.BodyFormat, dto.Attachments, dto.ToEmails, dto.CcEmails, dto.BccEmails, null);
            }
        }
        #endregion

        #endregion

        #region "受保护的方法"
        #region "增加邮件附件"
        /// <summary>
        /// 增加邮件附件
        /// </summary>
        /// <param name="fileName">文件名称</param>
        /// <param name="fileStream">文件流</param>
        /// <param name="mediaType">媒体类型(image,application)</param>
        /// <param name="mediaSubType">媒体子类型 </param>
        /// <returns></returns>
        protected virtual MimePart AddAttachment(string fileName, Stream fileStream, string mediaType = null, string mediaSubType = null)
        {
            Check.NotNullOrEmpty(fileName, nameof(fileName));
            Check.NotNull(fileStream, nameof(fileStream));
            MimePart attachment;
            if (!mediaType.IsMissing() && !mediaSubType.IsMissing())
            {
                attachment = new MimePart(mediaType, mediaSubType);
            }
            else
            {
                attachment = new MimePart();
            }

            attachment.ContentDisposition = new ContentDisposition(ContentDisposition.Attachment);
            attachment.ContentTransferEncoding = ContentEncoding.Base64;
            attachment.Content = new MimeContent(fileStream);
            attachment.FileName = fileName;

            return attachment;
        }
        #endregion

        #endregion

    }
    #endregion

}
